/*______________________________________________________________________________
 *
 * Copyright 2005 NORSYS
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * (1) Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 *
 * (2) Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in
 *     the documentation and/or other materials provided with the
 *     distribution.
 *
 * (3) The name of the author may not be used to endorse or promote
 *     products derived from this software without specific prior
 *     written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
 * OF THE POSSIBILITY OF SUCH DAMAGE.
 *______________________________________________________________________________
 *
 * Created on 26 sept. 2005
 * Author: Arnaud Bailly
 */
package speculoos.manager;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import speculoos.core.Mapper;
import speculoos.core.MapperException;
import speculoos.spi.Source;


/**
 * Base entry point for a business component.
 * <p>
 * There should be a single instance of MapperManager per instance of the
 * business component. This would normally imply that we must use a
 * <strong>Singleton </strong> pattern but we follow the widely accepted wisdom
 * that <quote>Singleton is Evil! </quote>. It is the responsibility of the
 * application developer to ensure that he uses one manager. If this is not the
 * case, behavior is unspecified as there may be conflicts with resources
 * associated with the manager that <em>are</em> statically allocated.
 * </p>
 * <p>
 * As its name implies, a <code>MapperManager</code> manages a collection of
 * <code>Mapper</code> objects. A manager can be in the following states:
 * <ul>
 * <li><strong>not configured </strong>: configuration has not completed</li>
 * <li><strong>configured </strong>: configuration has been completed but
 * manager has not been started</li>
 * <li><strong>ready </strong>: configuration has been completed and manager is
 * operating correctly</li>
 * </ul>
 * </p>
 * <p>
 * The design documentation gives a picture of the manager's life-cycle as an
 * automaton. See also the documentation of the interfaces {@link Manage}and
 * {@link Configure}for a description of their behavior as an automaton.
 * </p>
 * <h3>Constructor Issue</h3>
 * <p>
 * Following a run of JDepend, I found a cyclic dependency between Configurator and 
 * Manager due to constructor parameterized by Configurator. I removed the
 * constructor.
 * </p>
 * 
 * @author nono
 * @version $Id: MapperManager.java 259 2006-05-23 10:34:50Z /C=FR/ST=Nord/L=Lille/O=Norsys SA/OU=UE/CN=Arnaud Bailly/emailAddress=abailly@norsys.fr $
 * @see <a href="http://c2.com/cgi-bin/wiki?SingletonsAreEvil">Singletons Are
 *      Evil </a>
 * @see <a
 *      href="https://srvue.norsys.fr/doc/DAA_PTC_v410_FrameworkLDAP_v1maj0.doc">Architecture
 *      applicative </a>
 */
public class MapperManager implements Manage, Configure {

    /*
     * this manager's logger. Defaults to a logger named with this classes'name
     */
    private static final Log log = LogFactory.getLog(MapperManager.class);

    /*
     * are we configured ?
     */
    private boolean configured;

    /*
     * are we ready to operate ?
     */
    private boolean ready;

    /*
     * mappers
     */
    private Map /* < String, Mapper > */mappers = new HashMap();

    /*
     * data sources
     */
    private Map /* < String, Source > */sources = new HashMap();

    /*
     * mappers-ds association
     */
    private Map /* < Mapper, Source > */mapperToSource = new HashMap();

    /*
     * default parameter values
     */
    private Map /* < String, Object > */parameters = new HashMap();

    /*
     * current parameter values
     */
    private Map /* < String, Object > */environment = new HashMap();


    /**
     * Default constructor. Produces an unconfigured manager that cannot operate
     * properly without being configured.
     * 
     */
    public MapperManager() {
        this.configured = false;
        this.ready = false;
    }


    /**
     * Starts operating the manager.
     * 
     * @throws MapperException
     * 
     * @throws MapperConfigurationException
     */
    public void start() throws MapperException {
        this.configured = true;
        /* multiple calls to start do nothing */
        if (this.ready)
            return;
        /* allocate resources */
        for (Iterator it = sources.values().iterator(); it.hasNext();) {
            Map env = new HashMap(parameters);
            env.putAll(environment);
            ((Source) it.next()).start(env);
        }
        log.info("MapperManager started");
        /* done */
        this.ready = true;
    }

    /**
     * Returns the mapper associated with the given name in this manager.
     * 
     * @param name
     *            name identifying mapper. May not be null.
     * @return a mapper that is ready to operate.
     * @throws MapperConfigurationException
     */
    public Mapper getMapper(String name) throws MapperException {
        if (!configured || !ready)
            throw new MapperConfigurationException(
                    "Manager is not ready to operate.");
        Mapper mapper = (Mapper) mappers.get(name);
        if (mapper == null)
            throw new MapperConfigurationException("No mapper named " + name
                    + " in this manager");
        /* configure mapper through source */
        Source src = (Source) mapperToSource.get(mapper.getName());
        if (src == null)
            throw new MapperConfigurationException(
                    "No source is associated with mapper " + name);
        /* delegate preparation */
        Mapper niou = src.create(name, environment);
        return niou;
    }

    /**
     * Releases a mapper object which allows clean up and reuse of its
     * associated resources.
     * 
     * @param mapper
     *            the mapper object to release.
     */
    public void release(Mapper mapper) throws MapperException {
        if (mapper == null)
            throw new IllegalArgumentException("Cannot release null mapper !");
        /* configure mapper through source */
        Source src = (Source) mapperToSource.get(mapper.getName());
        if (src == null)
            throw new MapperConfigurationException(
                    "No source is associated with mapper " + mapper);
        /* delegate release */
        src.release(mapper);
    }

    /**
     * Stops the manager.
     * 
     */
    public void stop() {
        if(!ready)
            return;
        /* deallocate resources */
        /* allocate resources */
        for (Iterator it = sources.values().iterator(); it.hasNext();) {
            ((Source) it.next()).stop();
        }
        /* done */
        this.ready = false;
        log.info("MapperManager stopped");
    }

    /*
     * (non-Javadoc)
     * 
     * @see speculoos.core.core.Configure#addMapper(java.lang.String,
     *      speculoos.core.core.Mapper)
     */
    public void addMapper(String name, Mapper mapper)
            throws MapperConfigurationException {
        if (configured == true)
            throw new MapperConfigurationException("Manager is configured");
        if (mapper == null || name == null || "".equals(name))
            throw new IllegalArgumentException(
                    "Invalid arguments :"+name+", " +mapper);
        Object o = mappers.get(name);
        if (o != null)
            throw new MapperConfigurationException(name
                    + " is already defined as a mapper in this context");
        mappers.put(name, mapper);
        if(log.isDebugEnabled())
        	log.debug("adding mapper "+name+", class "+mapper.getClass());
    }

    /*
     * (non-Javadoc)
     * 
     * @see speculoos.core.core.Configure#addParameter(java.lang.String,
     *      java.lang.Object)
     */
    public void addParameter(String param, Object value)
            throws MapperConfigurationException {
        if (configured == true)
            throw new MapperConfigurationException("Manager is configured");
        if (param == null || "".equals(param) || value == null)
            throw new IllegalArgumentException(
                    "Invalid arguments :"+param);
        /* check */
        Object o = parameters.get(param);
        if (o != null)
            throw new MapperConfigurationException(param
                    + " is already defined in this context");
        parameters.put(param, value);
        if(log.isDebugEnabled())
        	log.debug("adding parameter "+param+", value="+value);
    }

    /*
     * (non-Javadoc)
     * 
     * @see speculoos.core.core.Configure#reset()
     */
    public void reset() throws MapperConfigurationException {
        if (ready)
            throw new MapperConfigurationException(
                    "Cannot reset this manager: stop it first");
        configured = false;
        ready = false;
        /* clear all maps */
        parameters.clear();
        sources.clear();
        environment.clear();
        mappers.clear();
        mapperToSource.clear();
        log.info("MapperManager reset");
    }

    /*
     * (non-Javadoc)
     * 
     * @see speculoos.core.core.Configure#set(java.lang.String,
     *      java.lang.Object)
     */
    public Object set(String param, Object value)
            throws MapperConfigurationException {
        if(this.configured || this.ready)
        	throw new MapperConfigurationException("Cannot set a variable after configuration is set");
        Object v;
        try {
            v = lookup(param);
        } catch (MapperException e) {
            throw new MapperConfigurationException("Name " + param
                    + " is not a valid parameter in this context");
        }
        this.environment.put(param, value);
        if(log.isDebugEnabled())
        	log.debug("setting parameter "+param+", value="+value);
        return v;
    }

    /**
     * Lookup name in this context. First name is looked up in
     * <code>environment</code>, if not found, it is looked up in
     * <code>parameters</code>. If not found, an exception is thrown.
     * 
     * @param param
     *            the name of the parameter to look up
     * @return value of this parameter in this environment
     * @throws MapperException
     *             if <code>param</code> is not defined in this context.
     */
    public Object lookup(String param) throws MapperException {
        Object o = environment.get(param);
        if (o == null) {
            o = parameters.get(param);
            if (o == null)
                throw new MapperConfigurationException("Name " + param
                        + " has not been found in this context");
        }
        return o;
    }

    /*
     * (non-Javadoc)
     * 
     * @see speculoos.core.core.Configure#setConfigured()
     */
    public void setConfigured() throws MapperConfigurationException {
        if (this.ready == true)
            throw new MapperConfigurationException("Manager already configured");
        this.configured = true;
        log.info("MapperManager configured");
    }

    /*
     * (non-Javadoc)
     * 
     * @see speculoos.core.core.Configure#unset(java.lang.String)
     */
    public Object unset(String param) throws MapperConfigurationException {
        Object o;
        try {
            o = lookup(param);
        } catch (MapperException e) {
            throw new MapperConfigurationException(e.getMessage());
        }
        environment.remove(param);
        if(log.isDebugEnabled())
        	log.debug("unsetting parameter "+param);
        return o;
    }

    /*
     * (non-Javadoc)
     * 
     * @see speculoos.core.core.Configure#addSource(java.lang.String,
     *      speculoos.core.core.spi.Source)
     */
    public void addSource(String name, Source source)
            throws MapperConfigurationException {
        if (configured == true)
            throw new MapperConfigurationException("Manager is configured");
        if (source == null || name == null || "".equals(name))
            throw new IllegalArgumentException(
                    "Incorrect arguments :"+name+", "+source);
        Object o = sources.get(name);
        if (o != null)
            throw new MapperConfigurationException("Name " + name
                    + " is already used for a source");
        /* store source */
        sources.put(name, source);
        if(log.isDebugEnabled())
        	log.debug("adding source "+name+", class="+source.getClass());
    }

    /*
     * (non-Javadoc)
     * 
     * @see speculoos.core.core.Configure#link(java.lang.String,
     *      java.lang.String)
     */
    public void link(String mapName, String srcName)
            throws MapperConfigurationException {
        Mapper m = (Mapper) mappers.get(mapName);
        if (m == null)
            throw new MapperConfigurationException(mapName
                    + " is not the name of a mapper");
        Source src = (Source) sources.get(srcName);
        if (src == null)
            throw new MapperConfigurationException(srcName
                    + " is not the name of a source");
        /* link */
        mapperToSource.put(m.getName(), src);
        if(log.isDebugEnabled())
            log.debug("linking map "+mapName+" to source "+src);
    }
}
